﻿using System.Threading;

namespace Honeycomb.Core
{   
    /// <summary>
    /// A blocking, thread safe queue implementation.
    /// </summary>
    /// <typeparam name="TType">Type of elements in the Queue.</typeparam>
    public class BlockingQueue<TType> : Queue<TType>
    {
        /// <summary>
        /// A semaphore is used to block Dequeue when the queue is empty.
        /// </summary>
        private readonly Semaphore blockDequeue = new Semaphore(0, int.MaxValue);

        /// <summary>
        /// A semaphore is used to block Enqueue when the queue is full.
        /// </summary>
        private readonly Semaphore blockEnqueue;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="maxLength"></param>
        public BlockingQueue(int maxLength)
        {
            blockEnqueue = new Semaphore(maxLength, int.MaxValue);
        }

        /// <summary>
        /// Dequeues the top element in the queue and returns it. If the queue 
        /// is empty the method blocks the calling thread until another thread 
        /// calls Enqueue to add an element to the queue.
        /// </summary>
        /// <returns>The first element in the queue.</returns>
        public override TType Dequeue()
        {
            blockDequeue.WaitOne();
            lock (queue)
            {
                TType value = base.Dequeue();
                blockEnqueue.Release();
                return value;
            }
        }

        /// <summary>
        /// Enqueues an element to the queue and returns the length of the queue. If the queue is 
        /// full the method blocks the calling thread until another thread calls Dequeue to remove
        /// an element from the queue.
        /// </summary>
        /// <param name="value">Value to enqueue.</param>
        /// <returns>Queue length after enqueue.</returns>
        public override int Enqueue(TType value)
        {
            blockEnqueue.WaitOne();
            lock (queue)
            {
                int count = base.Enqueue(value);
                blockDequeue.Release();
                return count;
            }
        }
    }
}